//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2003-2007
//
// Slice all polylines & polygons.
//

# include "StdAfx.h"
# include "Resource.h"
# include "SliceMap.h"
# include "UndoRedo.h"
# include "PtAlgo.h"
# include "PtTrim.h"
# include "Status.h"
# include "MapView.h"
# include "Settings.h"
# include "EditHelpers.h"
# include "SelectFolder.h"
# include "ParseHelpers.h"
# include "fast_floor.h"
# include "Globals.h"

const char c_strIniCell [] = "0.040";

////////////////////////

static
void _SplitPolypoints (const point_t & _A, const point_t & _B, bool _bPolygon, const polyepoints_t & _ptsIn, bool _bHasExtra, polyepoints_t & _ptsOut1, polyepoints_t & _ptsOut2, bool _bAddExternalNodes, rgn_t * _pRgn) {
	for (polyepoints_t::const_iterator iPart = _ptsIn.begin (); iPart != _ptsIn.end (); ++ iPart) {
		const epoints_t & rptsIn = * iPart;

		if (_bPolygon) {
			TrimPolygon  (_A, _B, rptsIn, _ptsOut1);
			TrimPolygon  (_B, _A, rptsIn, _ptsOut2);
		} else {
			SetTrimmingCtx (& g_map, rptsIn, _bAddExternalNodes);
			TrimPolyline (_A, _B, rptsIn, _bHasExtra, _ptsOut1, _pRgn);
			TrimPolyline (_B, _A, rptsIn, _bHasExtra, _ptsOut2, _pRgn);
			ResetTrimmingCtx ();
		}
	}
}

void SplitPolypointsByX (float _fX0, float _fXStep, int _iCell0, int _iCell1, bool _bPolygon, const polyepoints_t & _ptsIn, bool _bHasExtra, polyepoints_t & _ptsOut, bool _bAddExternalNodes, rgn_t * _pRgn) {
	const int iCellMean = (_iCell0 + _iCell1)/2;
	const point_t A (_fX0 + iCellMean*_fXStep, 0);
	const point_t B (_fX0 + iCellMean*_fXStep, 1);

	if (_iCell0 == _iCell1) {
		_SplitPolypoints (A, B, _bPolygon, _ptsIn, _bHasExtra, _ptsOut, _ptsOut, _bAddExternalNodes, _pRgn);
		return;
	}

	polyepoints_t ptsOut1, ptsOut2;
	_SplitPolypoints (A, B, _bPolygon, _ptsIn, _bHasExtra, ptsOut1, ptsOut2, _bAddExternalNodes, _pRgn);

	if (_iCell0 <= iCellMean - 1)
		SplitPolypointsByX (_fX0, _fXStep, _iCell0, iCellMean - 1, _bPolygon, ptsOut2, _bHasExtra, _ptsOut, _bAddExternalNodes, _pRgn);
	else
		_ptsOut.splice (_ptsOut.end (), ptsOut2);

	if (iCellMean + 1 <= _iCell1)
		SplitPolypointsByX (_fX0, _fXStep, iCellMean + 1, _iCell1, _bPolygon, ptsOut1, _bHasExtra, _ptsOut, _bAddExternalNodes, _pRgn);
	else
		_ptsOut.splice (_ptsOut.end (), ptsOut1);
}

void SplitPolypointsByY (float _fY0, float _fYStep, int _iCell0, int _iCell1, bool _bPolygon, const polyepoints_t & _ptsIn, bool _bHasPointExtras, polyepoints_t & _ptsOut, bool _bAddExternalNodes, rgn_t * _pRgn) {
	const int iCellMean = (_iCell0 + _iCell1)/2;
	const point_t A (0, _fY0 + iCellMean*_fYStep);
	const point_t B (1, _fY0 + iCellMean*_fYStep);

	if (_iCell0 == _iCell1) {
		_SplitPolypoints (A, B, _bPolygon, _ptsIn, _bHasPointExtras, _ptsOut, _ptsOut, _bAddExternalNodes, _pRgn);
		return;
	}

	polyepoints_t ptsOut1, ptsOut2;
	_SplitPolypoints (A, B, _bPolygon, _ptsIn, _bHasPointExtras, ptsOut1, ptsOut2, _bAddExternalNodes, _pRgn);

	if (_iCell0 <= iCellMean - 1)
		SplitPolypointsByY (_fY0, _fYStep, _iCell0, iCellMean - 1, _bPolygon, ptsOut1, _bHasPointExtras, _ptsOut, _bAddExternalNodes, _pRgn);
	else
		_ptsOut.splice (_ptsOut.end (), ptsOut1);

	if (iCellMean + 1 <= _iCell1)
		SplitPolypointsByY (_fY0, _fYStep, iCellMean + 1, _iCell1, _bPolygon, ptsOut2, _bHasPointExtras, _ptsOut, _bAddExternalNodes, _pRgn);
	else
		_ptsOut.splice (_ptsOut.end (), ptsOut2);
}

static
void _SliceRgn (rgn_t & _rgn, float _fX0, float _fXStep, float _fY0, float _fYStep) {
	const bool bPolygon = _rgn.IsPolygon ();

	bool bModified = false;

	rgn_elements_t & elements = _rgn.elements;
	const size_t cElements = elements.size ();
	for (size_t cElement = 0; cElement < cElements; ++ cElement) {
		rgn_element_t & element = elements [cElement];
		if (element.bDeleted)
			continue;

		points_t & points = element.points;
		if (points.empty ())
			continue;

		// Get split cells.
		const int iCellX0 = fast_floor ((element.rectBound.x0 - _fX0)/_fXStep);
		const int iCellX1 = fast_floor ((element.rectBound.x1 - _fX0)/_fXStep);
		const int iCellY0 = fast_floor ((element.rectBound.y0 - _fY0)/_fYStep);
		const int iCellY1 = fast_floor ((element.rectBound.y1 - _fY0)/_fYStep);
		if (iCellX0 == iCellX1 && iCellY0 == iCellY1)
			continue;

		polyepoints_t ptsIn;
		ptsIn.push_back (epoints_t ());
		Points2EPoints (points, element.pExtra ? & element.pExtra->PointExtras : NULL, ptsIn.back ());

		const bool bHasPointExtras = element.pExtra && ! element.pExtra->PointExtras.empty ();

		// Split by X.
		polyepoints_t ptsOut;
		if (iCellX0 != iCellX1)
			SplitPolypointsByX (_fX0, _fXStep, iCellX0 + 1, iCellX1, bPolygon, ptsIn, bHasPointExtras, ptsOut, false, & _rgn);
		else
			ptsOut.swap (ptsIn);

		// Split by Y.
		if (iCellY0 != iCellY1) {
			polyepoints_t ptsOut2;
			SplitPolypointsByY (_fY0, _fYStep, iCellY0 + 1, iCellY1, bPolygon, ptsOut, bHasPointExtras, ptsOut2, false, & _rgn);
			ptsOut.swap (ptsOut2);
		}

		bModified = true;

		// Save some attributes of the element to be aware of elements.resize().
		const size_t cLevel    = element.cLevel;
		const bool bHole       = element.bHole;

		// Delete this element.
		element.bDeleted = true;

		// Add new elements to the end of list.
		const size_t cNowElements = elements.size ();
		elements.resize (cNowElements + ptsOut.size ());
		size_t cNewElement = cNowElements;
		for (polyepoints_t::iterator iNewElement = ptsOut.begin (); iNewElement != ptsOut.end (); ++ iNewElement) {
			const epoints_t & epoints = * iNewElement;

			points_t points;
			point_extras_t PointExtras;
			EPoints2Points (epoints, bHasPointExtras, points, PointExtras);

			// NOTE: Splitting may create zero-area polygons or zero-length polylines.
			const bool bEmpty = bPolygon ? GetArea (points) < 1e-10 : GetLength (points) < 1e-5;

			assert (cNewElement < elements.size ());
			rgn_element_t & elemNew = elements [cNewElement ++];

			elemNew.cFileOffset = -1;
			elemNew.cLevel      = cLevel;
			elemNew.bHole       = bHole;
			if (! bEmpty) {
				elemNew.points.swap (points);

				if (bHasPointExtras && ! PointExtras.empty ())
					elemNew.pExtra->PointExtras.swap (PointExtras);
			} else
				elemNew.bDeleted = true;
		}
	}

	if (bModified) {
		_rgn.dwFlags |= rgn_t::fModified;
		_rgn.RefreshAttributes (g_map, false);
		_rgn.ReorderHoles ();
	}
}

////////////////////////////////////////

class CSliceMapAction : public CEditAction {
  public:
	CSliceMapAction (float _fX0, float _fXStep, float _fY0, float _fYStep) :
		m_fX0 (_fX0),
		m_fXStep (_fXStep),
		m_fY0 (_fY0),
		m_fYStep (_fYStep)
	{}

	virtual void GetName (string_t & _str) const {_str.append ("slice map");}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo () {}
	virtual void Redo () {}

	float m_fX0, m_fXStep, m_fY0, m_fYStep;
};

bool CSliceMapAction::Do () {
	CCritSec::CLock lock (g_cs);

	const size_t cTotalRgns = g_map.layers [rkPolyline].cRgns + g_map.layers [rkPolygon].cRgns;
	if (cTotalRgns == 0)
		return true;
	size_t cRgnsProcessed = 0;
	SetStatus (GetStr (IDS_STATUS_SLICING));
	SetProgressStatus (0);

	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		if (cKind != rkPolyline && cKind != rkPolygon)
			continue;

		layer_t & layer = g_map.layers [cKind];
		rgns_t & rgns = layer.rgns;

		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;

			_SliceRgn (rgn, m_fX0, m_fXStep, m_fY0, m_fYStep);

			++ cRgnsProcessed;
			SetProgressStatus (cRgnsProcessed*100/cTotalRgns);
		}
	}

	SetStatusReady ();

	return true;
}

void SliceMap (float _fX0, float _fXStep, float _fY0, float _fYStep) {
	PerformEditAction (new CSliceMapAction (_fX0, _fXStep, _fY0, _fYStep));
	Refresh ();
}

/////////////////////////////////////////////

struct cell_t {
	double fWidth, fHeight;
};

static
INT_PTR CALLBACK _SliceMapDialogProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			cell_t * const pCell = reinterpret_cast<cell_t *> (_lParam);
			::SetWindowLong (_hWnd, GWL_USERDATA, _lParam);

			char buf [32];

			::GetSetting ("SliceMap_Width", buf, sizeof (buf), c_strIniCell);
			::SetWindowText (::GetDlgItem (_hWnd, IDC_WIDTH), buf);

			::GetSetting ("SliceMap_Height", buf, sizeof (buf), c_strIniCell);
			::SetWindowText (::GetDlgItem (_hWnd, IDC_HEIGHT), buf);

			return TRUE;
		}

		case WM_COMMAND: {
			switch (LOWORD (_wParam)) {
				case IDC_WIDTH:
				case IDC_HEIGHT: {
					if (HIWORD (_wParam) == EN_CHANGE) {
						const HWND hWidth  = ::GetDlgItem (_hWnd, IDC_WIDTH);
						const HWND hHeight = ::GetDlgItem (_hWnd, IDC_HEIGHT);
						if (
							ValidateAsFloat (hWidth) &&
							ValidateAsFloat (hHeight) &&
							GetTextAsFloat (hWidth)  > 1e-4 &&
							GetTextAsFloat (hHeight) > 1e-4
						)
							::EnableWindow (::GetDlgItem (_hWnd, IDOK), TRUE);
						else
							::EnableWindow (::GetDlgItem (_hWnd, IDOK), FALSE);
					}
					return TRUE;
				}

				case IDOK: {
					cell_t * const pCell = reinterpret_cast<cell_t *> (::GetWindowLong (_hWnd, GWL_USERDATA));

					char buf [32];

					const HWND hWidth = ::GetDlgItem (_hWnd, IDC_WIDTH);
					::GetWindowText (hWidth, buf, sizeof (buf));
					SaveSetting ("SliceMap_Width", buf);
					pCell->fWidth  = GetTextAsFloat (hWidth);

					const HWND hHeight = ::GetDlgItem (_hWnd, IDC_HEIGHT);
					::GetWindowText (hHeight, buf, sizeof (buf));
					SaveSetting ("SliceMap_Height", buf);
					pCell->fHeight = GetTextAsFloat (hHeight);

					::EndDialog (_hWnd, 0);
					return TRUE;
				}

				case IDCANCEL:
					::EndDialog (_hWnd, 1);
					return TRUE;
			}
		}
	}
	return FALSE;
}

void SliceMap (HWND _hWnd) {
	cell_t cell;
	const INT_PTR nResult = DlgBox (IDD_SLICE_MAP, _hWnd, _SliceMapDialogProc, reinterpret_cast<LPARAM> (& cell));
	if (nResult == 1)
		// Dialog was canceled.
		return;

	if (cell.fWidth > 1e-4 && cell.fHeight > 1e-4)
		SliceMap (0.f, cell.fWidth, 0.f, cell.fHeight);
}

# include "SplitToFiles.inl"